package com.useful.featurewifi.feature.base.http

import androidx.annotation.Nullable
import retrofit2.*
import java.io.IOException
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.util.concurrent.Executor

/**
 * @author Afra55
 * @date 4/14/21
 * A smile is the best business card.
 *              static class ErrorBody {
                    String message;
                }

 *          // Look up a converter for the Error type on the Retrofit instance.
            Converter<ResponseBody, ErrorBody> errorConverter =
            retrofit.responseBodyConverter(ErrorBody.class, new Annotation[0]);
            // Convert the error body into our Error type.
            ErrorBody errorBody = errorConverter.convert(response.errorBody());
            System.out.println("ERROR: " + errorBody.message);
 */
/** A callback which offers granular callbacks for various conditions.  */
interface ApiCallback<T> {
    /** Called for [200, 300) responses.  */
    fun success(response: Response<T>?){
        onComplete()
    }

    /** Called for 401 responses.  */
    fun unauthenticated(response: Response<*>?){
        error(response)
    }

    /** Called for [400, 500) responses, except 401.  */
    fun clientError(response: Response<*>?){
        error(response)
    }

    /** Called for [500, 600) response.  */
    fun serverError(response: Response<*>?){
        error(response)
    }

    /** Called for network errors while making the call.  */
    fun networkError(e: IOException?){
        error(t = e)
    }

    /** Called for unexpected errors while making the call.  */
    fun unexpectedError(t: Throwable?){
        error(t = t)
    }

    fun error(response: Response<*>? = null, t:Throwable? = null){
        onComplete()
    }

    fun onComplete()
}

interface ApiCall<T> {
    fun cancel()
    fun enqueue(callback: ApiCallback<T>?)
    fun clone(): ApiCall<T> // Left as an exercise for the reader...
}


class ErrorHandlingCallAdapterFactory : CallAdapter.Factory() {
    @Nullable
    override fun get(
        returnType: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): CallAdapter<*, *>? {
        if (getRawType(returnType) != ApiCall::class.java) {
            return null
        }
        check(returnType is ParameterizedType) { "ApiCall must have generic type (e.g., ApiCall<ResponseBody>)" }
        val responseType =
            getParameterUpperBound(
                0,
                returnType
            )
        val callbackExecutor = retrofit.callbackExecutor()
        return ErrorHandlingCallAdapter<Any>(
            responseType,
            callbackExecutor
        )
    }

    private class ErrorHandlingCallAdapter<R> internal constructor(
        private val responseType: Type,
        private val callbackExecutor: Executor?
    ) :
        CallAdapter<R, ApiCall<R>> {
        override fun responseType(): Type {
            return responseType
        }

        override fun adapt(call: Call<R>): ApiCall<R> {
            return ApiCallAdapter(
                call,
                callbackExecutor
            )
        }

    }
}


/** Adapts a [Call] to [ApiCall].  */
internal class ApiCallAdapter<T>(
    private val call: Call<T>,
    private val callbackExecutor: Executor?
) :
    ApiCall<T> {
    override fun cancel() {
        call.cancel()
    }

    override fun enqueue(callback: ApiCallback<T>?) {
        call.enqueue(
            object : Callback<T> {
                override fun onResponse(
                    call: Call<T>,
                    response: Response<T>
                ) {
                    try {
                        when (response.code()) {
                            in 200..299 -> {
                                callback?.success(response)
                            }
                            401 -> {
                                callback?.unauthenticated(response)
                            }
                            in 400..499 -> {
                                callback?.clientError(response)
                            }
                            in 500..599 -> {
                                callback?.serverError(response)
                            }
                            else -> {
                                callback?.unexpectedError(RuntimeException("Unexpected response $response"))
                            }
                        }
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }

                override fun onFailure(
                    call: Call<T>,
                    t: Throwable
                ) {
                    try {
                        if (t is IOException) {
                            callback?.networkError(t)
                        } else {
                            callback?.unexpectedError(t)
                        }
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }
            })
    }

    override fun clone(): ApiCall<T> {
        return ApiCallAdapter(
            call.clone(),
            callbackExecutor
        )
    }


}
